package com.zhuhjay.service.imchat.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gitee.zhuhjay.xunfei.sdk.AppClientFactory;
import com.gitee.zhuhjay.xunfei.sdk.consts.Role;
import com.gitee.zhuhjay.xunfei.sdk.model.ApiResponse;
import com.gitee.zhuhjay.xunfei.sdk.model.Text;
import com.zhuhjay.common.base.constant.TimeConstant;
import com.zhuhjay.common.base.model.vo.HttpResult;
import com.zhuhjay.common.base.util.BeanBuilder;
import com.zhuhjay.common.base.util.JacksonUtils;
import com.zhuhjay.common.netty.util.NettySocketUtils;
import com.zhuhjay.common.redis.util.RedisUtils;
import com.zhuhjay.common.redis.util.RedissonUtils;
import com.zhuhjay.common.security.context.LoginUserContext;
import com.zhuhjay.common.web.model.domain.CursorReq;
import com.zhuhjay.common.web.model.domain.CursorResp;
import com.zhuhjay.service.imchat.constant.AiServiceConstant;
import com.zhuhjay.service.imchat.mapper.ChatMessageMapper;
import com.zhuhjay.service.imchat.model.dto.ChatMessageDto;
import com.zhuhjay.service.imchat.model.entity.ChatMessage;
import com.zhuhjay.service.imchat.model.vo.ChatMessageVo;
import com.zhuhjay.service.imchat.service.ChatMessageService;
import com.zhuhjay.service.system.model.entity.SystemUser;
import com.zhuhjay.service.system.model.vo.ChatUserOnlineVo;
import com.zhuhjay.service.system.service.SystemConfigService;
import com.zhuhjay.service.system.service.SystemUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author ZhuHJay
 * @since 2023-07-11
 */
@Service
public class ChatMessageServiceImpl extends ServiceImpl<ChatMessageMapper, ChatMessage> implements ChatMessageService {

    @Resource
    private SystemUserService userService;
    @Resource
    private ExecutorService executorService;
    @Resource
    private SystemConfigService configService;
    /** AI 服务客户端: 防止出现空指针 -> 若没有给api配置的话 */
    @Autowired(required = false)
    private AppClientFactory appClientFactory;

    @Override
    public CursorResp<ChatMessageVo> cursor(CursorReq cursorReq) {
        // 此时先使用id作为游标
        long cursorId;
        if (cursorReq.hasCursor()) {
            cursorId = Long.parseLong(cursorReq.getCursor());
        } else {
            cursorId = Long.MAX_VALUE;
        }
        List<ChatMessage> list = this.lambdaQuery()
                .eq(ChatMessage::getStatus, 1)
                .orderByDesc(ChatMessage::getId)
                .lt(ChatMessage::getId, cursorId).last("limit " + cursorReq.getSize()).list();
        Collections.reverse(list);

        CursorResp<ChatMessageVo> resp = new CursorResp<>();
        resp.setRecords(list.stream().map(msg -> {
            ChatMessageVo vo = ChatMessageVo.toVo(msg);
            // AI 消息
            if (msg.getUserId().intValue() == AI_USER.getId().intValue()) {
                vo.setUserInfo(ChatUserOnlineVo.toVo(AI_USER));
                return vo;
            }
            SystemUser user = userService.getById(msg.getUserId());
            vo.setUserInfo(ChatUserOnlineVo.toVo(user));
            return vo;
        }).collect(Collectors.toList()));
        // 返回真实数量
        resp.setSize(list.size());
        if (!list.isEmpty()) {
            Long lastId = list.get(0).getId();
            resp.setCursor(String.valueOf(lastId));
            resp.setHasNext(lastId > 0 && list.size() == cursorReq.getSize());
        }
        return resp;
    }

    @Override
    public void sendMessage(ChatMessageDto dto, String eventName) {
        // 验证是否本人
        String username = LoginUserContext.getUsername();
        if (!LoginUserContext.getUserId().equals(dto.getUserId()) || !username.equals(dto.getUsername())) {
            return;
        }
        ChatMessage message = ChatMessageDto.toJavaBean(dto);
        // 组装用户信息
        ChatMessageVo vo = ChatMessageVo.toVo(message);
        vo.setUserInfo(BeanBuilder.builder(ChatUserOnlineVo::new)
                .with(ChatUserOnlineVo::setUsername, username)
                .with(ChatUserOnlineVo::setAvatar, dto.getAvatar())
                .build());

        this.save(message);
        vo.setId(message.getId());
        NettySocketUtils.sendEvent2All(eventName, HttpResult.ok(vo));

        // 如果连接工厂为空, 那么就不执行 AI 任务
        if (appClientFactory == null) {
            return;
        }
        // 如果关闭了 AI 服务, 那么就不执行 AI 任务
        String configValue = configService.getConfigValue("system.ai.enable");
        if (!BooleanUtil.toBoolean(configValue)) {
            return;
        }
        // AI 消息处理
        aiHandlerMessage(message, username, eventName);
    }

    private static final SystemUser AI_USER = BeanBuilder.builder(SystemUser::new)
            .with(SystemUser::setId, 0)
            .with(SystemUser::setUsername, "AI")
            .with(SystemUser::setAvatar, "https://img.zcool.cn/community/01f0865d45230ba8012187f485a17b.jpg@1280w_1l_2o_100sh.jpg")
            .build();

    private void aiHandlerMessage(ChatMessage message, String formUser, String eventName) {
        String userQuestion = message.getContent();
        if (!userQuestion.startsWith(AiServiceConstant.AI_CHAT_START_WITH)) {
            return;
        }
        String businessKey = "ai:lock:" + formUser;
        boolean isLock = RedissonUtils.withFastFailTryLock(businessKey, () -> {
            // 是否还有次数
            String counterKey = StrUtil.format(AiServiceConstant.AI_INVOKE_COUNTER_KEY_TMP,
                    DateUtil.format(DateUtil.date(), TimeConstant.DATE_FORMAT),
                    formUser);
            String counter = RedisUtils.getCache(counterKey);
            if (StrUtil.isNotBlank(counter)) {
                int count = Integer.parseInt(counter);
                // 没有次数, 发送消息告知用户, 并将消息保存
                if (count >= 5) {
                    aiNotice(message, StrUtil.format("@{} 今日次数已用完, 感谢您的使用, 明日再见😊!", formUser), eventName);
                    return;
                }
            } else {
                // 设置次数
                RedisUtils.setCache(counterKey, 0);
            }
            // 执行 AI 任务
            executorService.execute(() -> {
                // 执行任务必须拿到锁
                RedissonUtils.withInfiniteWaitLock(businessKey, () -> {
                    // 处理消息
                    String c = userQuestion.substring(4);
                    String historyKey = AiServiceConstant.AI_HISTORY_KEY_PREFIX + formUser;
                    List<String> historyCache = RedisUtils.getMembers4List(historyKey);
                    List<Text> history = new ArrayList<>();

                    // 收集历史数据
                    if (!historyCache.isEmpty()) {
                        history.addAll(historyCache.stream()
                                .map(s -> JacksonUtils.readValue(s, Text.class))
                                .collect(Collectors.toList())
                        );
                        // 最多保留4条历史记录
                        if (history.size() > 4) {
                            history = history.subList(history.size() - 4, history.size());
                        }
                    }

                    // 调用 AI 服务
                    Future<ApiResponse> future = appClientFactory
                            .openClientV2().sendAsync(c, history.toArray(new Text[0]));

                    // 在此过程可以做些事情
                    Text questionText = Text.builder()
                            .content(c)
                            .role(Role.USER.getRole())
                            .build();
                    // 没有历史数据, 需要填充并设置过期时间
                    RedisUtils.addValues2List(historyKey, questionText);
                    if (history.isEmpty()) {
                        RedisUtils.setKeyExpire(historyKey, 30, TimeUnit.MINUTES);
                    }

                    // 处理结果
                    try {
                        ApiResponse response = future.get(20, TimeUnit.SECONDS);
                        if (response.getOk()) {
                            String answer = response.getAnswer();
                            // 缓存历史数据
                            Text answerText = Text.builder()
                                    .content(answer)
                                    .role(Role.ASSISTANT.getRole())
                                    .build();
                            RedisUtils.addValues2List(historyKey, answerText);
                            // 执行成功, 统计次数
                            RedisUtils.increment(counterKey);
                            // 发送消息
                            aiNotice(message,
                                    StrUtil.format("@{} {}\n\t---> 本次问答💬共消费 {} tokens",
                                            formUser, answer, response.getTotalTokens()),
                                    eventName);
                        } else {
                            // 执行失败, 发送消息告知用户, 并将消息保存
                            aiNotice(message, StrUtil.format("@{} 执行失败, 失败原因: {}", formUser, response.getMessage()), eventName);
                        }
                    } catch (TimeoutException e) {
                        log.error("AI 服务超时", e);
                        // 执行超时, 发送消息告知用户, 并将消息保存
                        aiNotice(message, StrUtil.format("@{} 使用过于火爆🔥, 请稍后再试!", formUser), eventName);
                    } catch (Exception e) {
                        log.error("AI 服务异常", e);
                        // 执行失败, 发送消息告知用户, 并将消息保存
                        aiNotice(message, StrUtil.format("@{} 服务异常🆘, 请稍后再试!", formUser), eventName);
                    }
                });
            });
        });
        if (!isLock) {
            // 没有拿到锁就是在处理中, 发送消息告知用户
            aiNotice(message, StrUtil.format("@{} 正在加急处理⏳, 请耐心等待!", formUser), eventName);
        }
    }

    /** AI 消息处理 */
    private void aiNotice(ChatMessage message, String content, String eventName) {
        // 清空 id!!!
        message.setId(null);
        // 消息传递者为 AI!!!
        message.setUserId(AI_USER.getId());
        message.setContent(content);
        message.setCreateTime(new Date());
        this.save(message);
        ChatMessageVo vo = ChatMessageVo.toVo(message);
        vo.setUserInfo(ChatUserOnlineVo.toVo(AI_USER));
        // 消息体需要携带 id给客户端!!!
        vo.setId(message.getId());
        NettySocketUtils.sendEvent2All(eventName, HttpResult.ok(vo));
    }

}
